﻿using CSRedis;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace AIStudio.ConSole.Redis.Ch05
{
    class TestCh05
    {
        Dictionary<logging, string> SEVERITY = new Dictionary<logging, string>()
        {
            { logging.DEBUG, "debug" },
            { logging.INFO, "info" },
            { logging.WARNING, "warning" },
            { logging.ERROR, "error" },
            { logging.CRITICAL, "critical" },
        };

        private bool QUIT = false;
        private int SAMPLE_COUNT = 100;

        public void log_recent(CSRedisClient conn, string name, string message, logging severity = logging.DEBUG, CSRedisClientPipe<string> pipe = null)
        {
            var severity1 = SEVERITY[severity];
            var destination = $"recent:{name}:{severity1}";
            message = DateTime.Now.ToLongDateString() + ' ' + message;
            if (pipe == null)
            {
                pipe = conn.StartPipe();
            }
            pipe.LPush(destination, message);
            pipe.LTrim(destination, 0, 99);
            pipe.EndPipe();
        }

        public void log_common(CSRedisClient conn, string name, string message, logging severity = logging.INFO,int timeout= 5)
        {
            var severity1 = SEVERITY[severity];
            var destination = $"recent:{name}:{severity1}";
            var start_key = destination + ":start";
            var pipe = conn.StartPipe();
            var end = DateTime.Now.AddSeconds(timeout);
            while (DateTime.Now < end)
            {
                try
                {
                    var hour_start = DateTime.UtcNow.ToString("yyyy-MM-dd HH:00:00");
                    var existing = conn.Get(start_key);
                    DateTime hour_start_dt = DateTime.ParseExact(hour_start, "yyyy-MM-dd HH:mm:ss", System.Globalization.CultureInfo.CurrentCulture);
                    DateTime existing_dt = DateTime.ParseExact(existing, "yyyy-MM-dd HH:mm:ss", System.Globalization.CultureInfo.CurrentCulture);
                    if (!string.IsNullOrEmpty(existing) && existing_dt < hour_start_dt)
                    {
                        pipe.Rename(destination, destination + ":last");
                        pipe.Rename(start_key, destination + ":pstart");
                        pipe.Set(start_key, hour_start);
                    }
                    else if (string.IsNullOrEmpty(existing))
                    {
                        pipe.Set(start_key, hour_start);
                    }

                    pipe.ZIncrBy(destination, message, 1);
                    log_recent(conn, name, message, severity, pipe);
                    return;
                }
                catch
                {
                    continue;
                }
            }

        }

        int[] PRECISION = { 1, 5, 60, 300, 3600, 18000, 86400 };

        public void update_counter(CSRedisClient conn, string name, int count = 1, double? now = null)
        {
            if (now == null)
            {
                now = (DateTime.Now - new DateTime(1970, 1, 1)).TotalSeconds;
            }
            var pipe = conn.StartPipe();

            foreach(var prec in PRECISION)
            {
                var pnow = (now / prec) * prec;
                var hash = $"{prec}:{name}";
                pipe.ZAdd($"known:{hash}");
                pipe.HIncrBy("count:" + hash, pnow.ToString(), count);
            }

            pipe.EndPipe();
        }

        public List<string> get_counter(CSRedisClient conn, string name, string precision)
        {
            var hash = $"{precision}:{name}";
            var data = conn.HGetAll($"count:{hash}");
            List<string> to_return = new List<string>();
            foreach(var keyvalue in data)
            {
                to_return.Add($"{keyvalue.Key},{keyvalue.Value}");
            }

            return to_return;
        }

        public void clean_counters(CSRedisClient conn)
        {
            var pipe = conn.StartPipe();
            int passes = 0;
            while(!QUIT)
            {
                var start = DateTime.Now;
                int index = 0;
                while(index < conn.ZCard("known:"))
                {
                    var hash = conn.ZRange("known:", index, index)[0];
                    index += 1;
                    if (hash == null)
                        break;
                    var prec = int.Parse(hash.Split(':')[0]);
                    var bprec = prec / 60;

                    if (passes % bprec == 0)
                        continue;

                    var hkey = "count:" + hash;

                    var cutoff = (DateTime.Now.AddSeconds(- SAMPLE_COUNT * prec) - new DateTime(1970, 1, 1)).TotalSeconds;
                    var samples = conn.HKeys(hkey);

                    var remove = new List<string>(samples);
                    remove.Add(cutoff.ToString());

                    if (remove.Count > 0)
                    {
                        conn.HDel(hkey, remove.ToArray());
                        if (remove.Count == samples.Length)
                        {
                            try
                            {
                                if(conn.HLen(hkey) > 0)
                                {
                                    pipe.ZRem("known:", hash);
                                    pipe.EndPipe();
                                    index -= 1;
                                }
                            }
                            catch
                            {
                                continue;
                            }
                        }
                    }
                }
                passes += 1;
                var duration = Math.Min((DateTime.Now - start).TotalSeconds + 1, 60);
                Thread.Sleep((int)Math.Max((60 - duration) * 1000, 1000));

            }
        }
    }

    public enum logging
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        CRITICAL,
    }
}
